/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */

package com.liycloud.gateway.filter;

import com.liycloud.common.core.constant.SecurityConstants;

//
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;

//
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.stream.Collectors;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;

/**
 * @author lengleng
 * @date 2018/10/8
 * <p>
 * 全局拦截器，作用所有的微服务
 *
 * <p>
 * 	1. 对请求头中参数进行处理 from 参数进行清洗
 * 	2. 重写 StripPrefix = 1, 支持全局
 *
 * <p>
 * 	支持swagger添加X-Forwarded-Prefix header （F SR2 已经支持，不需要自己维护）
 *
 *
 * 	@Inner 的诞生:
 *
 *  Pigx种Url的访问大致分为两种，一种是由Gateway网关从外网路由进来，一种是内网通过Feign进行内部服务调用。那我们结合Security就存在以下几种应用场景：
 *
 * 1. 外部从Gateway访问，需要鉴权（eg.CURD操作）。这种是最常使用的，用户登录后正常访问接口，不需要我们做什么处理（可能有的接口需要加权限字段）。
 *
 * 2. 外部从Gateway访问，不需要鉴权（eg.短信验证码）。需要我们将uri加入到security.oauth2.client.ignore-urls配置中，可以不需要鉴权访问
 *
 * 3. 内部服务间用Feign访问，不需要鉴权（eg.Auth查询用户信息）。也是需要我们将uri加入到security.oauth2.client.ignore-urls配置中，
 *    那与第二种的区别就是这种情况下大多数都是服务可以请求另一个服务的所有数据，不受约束.
 *    那我们如果仅仅只配置ignore-url的话，外部所有人都可以通过url请求到我们内部的链接，安全达不到保障。
 *    所以这里需要和第二种做一个区分, 使用 @Inner 注解来定义内部接口, 只能是内部服务之间通过 Feign 调用, 外部不能调用
 *    其实这里也可以修改 @Inner 的 value = false 这样就是第二种情形 security.oauth2.client.ignore-urls 外部可以直接访问了
 *
 *    鉴于上述第三种情况，我们配置了ignore-url 和 Feign，此时该接口不需要鉴权，服务内部通过Feign访问，服务外部通过url也可以访问，
 *
 *    所以Pigx中，加入了一种@RequestHeader(SecurityConstants.FROM)的处理方式。
 *
 *    即在接口方法中，对头部进行判断，只有请求带上相应的Header参数时 from = xx ，才允许通过访问，否则抛出异常。
 *
 *    那这时候其实我们在外网通过Gateway访问的时候，也可以手动带上这个Header参数，来达到这个目的。
 *    所以我们便在Gateway中设置了一个 GlobalFilter过滤器： PigxRequestGlobalFilter;
 *
 *
 *   外部请求必须是走网关的, 即便是外部请求自己模拟添加了 Header 请求头参数 form=FROM_IN,
 *   通过网关的 此Filter时也会把它清理掉, 后续进入内部服务时, 服务的调用链就不再含有该请求头的 form 参数, 外部模拟 内部调用行不通
 *
 *
 */
@Component
public class PigxRequestGlobalFilter implements GlobalFilter, Ordered {

	/**
	 * Process the Web request and (optionally) delegate to the next {@code WebFilter} through the given {@link GatewayFilterChain}.
	 */
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

		// 1. 清洗请求头中 from 参数
		ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> httpHeaders.remove(SecurityConstants.FROM)).build();

		// 2. 重写 StripPrefix, 路由时 会将url 中的将第一个前缀去掉
		// 相当于每个 路由配置是 都加上了一个filter  StripPrefix=1 , 去掉前缀
		// 例如:  /auth/mobile/token/sms,  真实服务的路由url是: /mobile/token/sms, 过滤器会去掉 StripPrefix=1 的第一个 /auth/
		addOriginalRequestUrl(exchange, request.getURI());
		String rawPath = request.getURI().getRawPath();
		String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/")).skip(1L).collect(Collectors.joining("/"));

		ServerHttpRequest newRequest = request.mutate().path(newPath).build();

		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());

		return chain.filter(exchange.mutate().request(newRequest.mutate().build()).build());
	}

	@Override
	public int getOrder() {
		return -1000;
	}

}
